home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
MacWorld 1997 January
/
Macworld (1997-01).dmg
/
Shareware World
/
Utilities
/
Data & Time Management
/
MacCalendar
/
Src
/
MacCalendar.c
< prev
next >
Wrap
C/C++ Source or Header
|
1994-10-20
|
22KB
|
749 lines
/* MacCalendar.c */
/*
* MacCalendar.c
* Copyright © 1993-94 Apple Computer Inc. All rights reserved.
* Based on the Status Bar Sample.c by Steve Christensen.
*
* File Type sdev
* File Creator SCAL -- registered with DTS
* Resource Type sdev
* Resource ID 0
* Resource Attributes purgeable
*
* Other options (MetroWerks, Think C 7.0):
* Set Require Prototypes, Check Pointer Types, All other warnings.
* Do not set trigraph recognition.
* Enable Apple extensions.
* MetroWerks link/project options:
* Link single segment
* Set Project type "Code Segment", Standard Header.
*/
#ifndef SystemSevenOrLater
#define SystemSevenOrLater 1
#endif
#include <GestaltEqu.h>
#include <Fonts.h>
#include <Memory.h>
#include <Menus.h>
#include <Quickdraw.h>
#include <Resources.h>
#include <ToolUtils.h>
#include <Types.h>
#include <Windows.h>
#include <Icons.h>
#ifdef MPW /* Defined in ETO 15 headers */
#include <ControlStrip.h>
#else
#include "ControlStrip.h"
#endif
#include "MacCalendar.h"
/*
* TEMP until universal headers are available
*/
#if 0
#if defined(powerc) || defined (__powerc)
#pragma options align=mac68k
#endif
struct QDGlobals {
char privates[76];
long randSeed;
BitMap screenBits;
Cursor arrow;
Pattern dkGray;
Pattern ltGray;
Pattern gray;
Pattern black;
Pattern white;
GrafPtr thePort;
};
#if defined(powerc) || defined(__powerc)
#pragma options align=reset
#endif
typedef struct QDGlobals QDGlobals;
#endif /* MPW */
/*
* Metrowerks uses A4 to reference globals. The A4-setup code was copied from the
* WDEF.c sample included in the Metrowerks DR3 distribution. MPW and Think C
* use PC-relative addressing in a single-segment code module.
*/
#ifdef __MWERKS__
#include "A4Stuff.h" // also included in <MacHeaders>
#include "SetupA4.h" // required to handle callback functions
#endif
/*
* Define the patterns as C-strings so they can be addressed as constants
* within the program.
*/
#define kWhitePattern ((ConstPatternParam) "\000\000\000\000\000\000\000\000")
#define kBlackPattern ((ConstPatternParam) "\377\377\377\377\377\377\377\377")
#define pstrcpy(dst, src) (BlockMove((src), (dst), (src)[0] + 1))
#define width(rect) ((rect).right - (rect).left)
#define height(rect) ((rect).bottom - (rect).top)
/*
* MyGetQDGlobals can be used to point to the current (A5) QuickDraw globals.
*/
#define MyGetQDGlobals() \
((QDGlobals *) (*((long *) SetCurrentA5()) - (sizeof (QDGlobals) - sizeof (GrafPtr))))
/*
* This record defines the information we need to draw the calendar. It is initialized
* when we are called with the sdevInitModule message, and passed to and from the
* Status Bar manager.
*/
typedef struct GlobalRecord {
Handle iconSuite; /* Status bar icon */
Handle textStrings; /* Balloon help string etc. */
StringHandle dateStringHandle; /* The date string preference */
PicHandle rightArrowPicture; /* Popup arrow */
short fontNumber; /* Preference: display font */
short fontSize; /* Preference: display font size */
short firstDayOfWeek; /* Sunday == 1 */
} GlobalRecord, *GlobalPtr, **GlobalHandle;
/*
* We access the global information through a macro. For example, GLOBAL.iconSuite is
* the icon suite handle in the global record.
*/
#define GLOBAL (*globalPtr)
#define PicFrame(what) ((**GLOBAL.what).picFrame) /* The picture frame rectangle */
/*
* Define the local/global prototypes.
*/
pascal long main(
unsigned long message,
GlobalHandle globalHandle,
const Rect *statusRect,
GrafPtr statusPort
);
long CtlStripInitialize(void);
void CtlStripCleanUp(
GlobalHandle globalHandle
);
long CtlStripPeriodicTickle(
GlobalPtr globalPtr,
const Rect *statusRect
);
long CtlStripMouseClick(
GlobalPtr globalPtr,
const Rect *statusRect
);
long CtlStripDrawStatusIcon(
GlobalPtr globalPtr,
const Rect *statusRect
);
OSErr CtlStripSavePreferences(
GlobalPtr globalPtr
);
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* main
*
* This "main" program is called by the Control Strip manager.
*/
pascal long
main(
unsigned long message,
GlobalHandle globalHandle,
const Rect *statusRect,
GrafPtr statusPort
)
{
#ifdef __MWERKS__
long oldA4 = SetCurrentA4();
#endif
short savedState;
register GlobalPtr globalPtr;
long result;
Str255 helpString;
if (0) { /* Unused variable */
statusPort;
}
if (((long) globalHandle) != 0) {
/*
* We have already allocated the global record. Save its lock state, lock
* the handle, and get the global pointer (so we can write GLOBAL.something)
*/
savedState = HGetState((Handle) globalHandle);
HLock((Handle) globalHandle);
globalPtr = *globalHandle;
}
result = 0; /* Unknown message result */
switch (message) {
case sdevInitModule: /* Initialize the module */
/*
* Initialization always sets globalHandle to NULL to avoid the HSetState
* at the exit routine. If CtlStripInitialize succeeds, it sets the result
* to the global parameter.
*/
globalHandle = NULL;
result = CtlStripInitialize(); /* Do the initialization and */
break; /* Return global or error code */
case sdevCloseModule: /* Clean up before closing */
CtlStripCleanUp(globalHandle);
globalHandle = NULL;
break;
case sdevFeatures: /* Return feature bits */
result = ( (1<<sdevWantMouseClicks) /* We handle mouse down */
| (1<<sdevDontAutoTrack) /* We track the mouse, too */
| (1<<sdevHasCustomHelp) /* Custom help string */
);
break;
case sdevGetDisplayWidth: /* Return display width */
result = kIconWidth + width(PicFrame(rightArrowPicture));
break;
case sdevPeriodicTickle: /* Nothing else is happening */
result = CtlStripPeriodicTickle(globalPtr, statusRect);
break;
case sdevDrawStatus: /* Draw the status bar info */
result = CtlStripDrawStatusIcon(globalPtr, statusRect);
break;
case sdevMouseClick: /* Status bar click */
result = CtlStripMouseClick(globalPtr, statusRect);
break;
case sdevSaveSettings: /* Save changed settings */
result = CtlStripSavePreferences(globalPtr);
break;
case sdevShowBalloonHelp: /* Display custom balloon help */
/*
* We don't really have a custom help string, but this shows how to do it.
*/
SBGetDetachedIndString(helpString, GLOBAL.textStrings, kStringHelp);
SBShowHelpString(statusRect, helpString);
break;
}
if (globalHandle != NULL) /* If we have globals allocated */
HSetState((Handle) globalHandle, savedState); /* Restore lock state */
#ifdef __MWERKS__
SetA4(oldA4);
#endif
return (result);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Initialization
*/
long
CtlStripInitialize(void)
{
register GlobalHandle globalHandle;
register GlobalPtr globalPtr;
long result;
Str255 work;
SavedSettingsHandle prefsHandle;
Handle *theIconSuite;
Boolean needSave;
long tempLong;
#define PREF (**prefsHandle)
needSave = FALSE;
globalHandle = (GlobalHandle) NewHandleClear(sizeof (GlobalRecord));
result = noErr;
if (globalHandle == NULL)
result = MemError();
else {
HLock((Handle) globalHandle);
globalPtr = *globalHandle;
/*
* Load and detach the icon suite
*/
theIconSuite = &GLOBAL.iconSuite;
result = SBGetDetachIconSuite(theIconSuite, ICON_StatusBar, svAllSmallData);
}
if (result == noErr) {
GLOBAL.textStrings = GetResource('STR#', STRN_Info);
result = ResError();
}
if (result == noErr) {
DetachResource(GLOBAL.textStrings);
GLOBAL.rightArrowPicture = GetPicture(PICT_RightArrow);
if (GLOBAL.rightArrowPicture == NULL)
result = ResError();
}
if (result == noErr) {
DetachResource((Handle) GLOBAL.rightArrowPicture);
/*
* Get the saved preferences, if any, and configure the drawing
* environment. Note that the sample status bar doesn't dispose
* of the prefsHandle.
*/
SBGetDetachedIndString(work, GLOBAL.textStrings, kStringPreference);
result = SBLoadPreferences(work, (Handle *) &prefsHandle);
if (result == noErr
&& GetHandleSize((Handle) prefsHandle) == sizeof (SavedSettings)
&& PREF.signature == kApplicationCreator
&& PREF.prefVersion == kPrefVersion
&& PREF.firstDayOfWeek >= kFirstIsSunday
&& PREF.firstDayOfWeek <= kFirstIsMonday) {
/*
* Use the saved preference resource
*/
pstrcpy(work, PREF.fontName);
GetFNum(work, &GLOBAL.fontNumber);
GLOBAL.fontSize = PREF.fontSize;
GLOBAL.firstDayOfWeek = PREF.firstDayOfWeek;
pstrcpy(work, PREF.dateString); /* This must be last */
}
else {
/*
* Hmm, we don't have any preferences. Build a new preference resource.
*/
result = noErr;
SBGetDetachedIndString(work, GLOBAL.textStrings, kStringFontName);
GetFNum(work, &GLOBAL.fontNumber);
SBGetDetachedIndString(work, GLOBAL.textStrings, kStringFontSize);
StringToNum(work, &tempLong);
GLOBAL.fontSize = tempLong;
SBGetDetachedIndString(work, GLOBAL.textStrings, kStringFirstDayOfWeek);
StringToNum(work, &tempLong);
GLOBAL.firstDayOfWeek = tempLong;
/*
* This must be last as we will store work in the dateStringHandle
*/
SBGetDetachedIndString(work, GLOBAL.textStrings, kStringDayNames);
needSave = TRUE;
}
GLOBAL.dateStringHandle = NewString(work);
if (GLOBAL.dateStringHandle == NULL)
result = MemError();
}
/*
* We've finished all initialization. If there is an error, exit through
* CtlStripCleanUp to dispose of handles and other junk. If initialization
* are successful, unlock the handle and return the handle cast to a long.
*/
if (result != noErr) {
CtlStripCleanUp(globalHandle);
result = 0;
}
else {
if (needSave)
(void) CtlStripSavePreferences(globalPtr);
HUnlock((Handle) globalHandle);
result = (long) globalHandle;
}
return (result);
#undef PREF
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Termination
*/
void
CtlStripCleanUp(
GlobalHandle globalHandle
)
{
register GlobalPtr globalPtr;
if ((long) globalHandle > 0) {
HLock((Handle) globalHandle);
globalPtr = *globalHandle;
if (GLOBAL.iconSuite != NULL)
DisposeIconSuite(GLOBAL.iconSuite, TRUE);
if (GLOBAL.textStrings != NULL)
DisposeHandle(GLOBAL.textStrings);
if (GLOBAL.rightArrowPicture != NULL)
DisposeHandle((Handle) GLOBAL.rightArrowPicture);
if (GLOBAL.dateStringHandle != NULL)
DisposeHandle((Handle) GLOBAL.dateStringHandle);
DisposeHandle((Handle) globalHandle);
}
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Draw the icon in the status bar.
*/
long
CtlStripDrawStatusIcon(
GlobalPtr globalPtr,
const Rect *statusRect
)
{
Rect viewRect;
short arrowHeight;
viewRect = *statusRect;
viewRect.right = viewRect.left + kIconWidth;
(void) PlotIconSuite(&viewRect, atNone, ttNone, GLOBAL.iconSuite);
/*
* Draw an right-arrow to show that we have a popup menu. Well, we don't
* actually have a popup menu, but we do pop up a calendar when clicked on.
*/
arrowHeight = height(PicFrame(rightArrowPicture));
viewRect.left = viewRect.right;
viewRect.right += width(PicFrame(rightArrowPicture));
viewRect.top += ((height(viewRect) - arrowHeight) >> 1);
viewRect.bottom = viewRect.top + arrowHeight;
DrawPicture(GLOBAL.rightArrowPicture, &viewRect);
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Position the calendar with respect to the status bar. If there is enough room
* above, put it above, else put it below. Left and right operate similarly.
* 1.0d7 - extensive changes by Frederic Misery to prevent the calendar from being
* displayed across two monitors.
*/
static void
GetDisplayRect(
const Rect *statusRect,
Point displaySize,
Rect *windowRect
)
{
long gestaltResult;
Rect sBarRect;
Rect limitRect;
GDHandle statusDevice;
#define kCtlStripFrame 4 /* The frame above/below the icon itself */
sBarRect = *statusRect;
LocalToGlobal((Point *) &sBarRect.top);
LocalToGlobal((Point *) &sBarRect.bottom);
statusDevice = NULL;
/*
* Find the graphics device that contains the largest part of the statusRect.
*/
if (Gestalt(gestaltQuickdrawVersion, &gestaltResult) == noErr
&& gestaltResult >= gestalt8BitQD) {
unsigned long deviceArea;
unsigned long maxArea;
Rect tempRect;
GDHandle theDevice;
maxArea = 0;
for (theDevice = GetDeviceList();
theDevice != NULL;
theDevice = GetNextDevice(theDevice)) {
if (SectRect(&(**theDevice).gdRect, &sBarRect, &tempRect)) {
deviceArea = width(tempRect) * height(tempRect);
if (deviceArea > maxArea) {
maxArea = deviceArea;
statusDevice = theDevice;
}
}
}
}
limitRect = (statusDevice != NULL)
? (**statusDevice).gdRect
: MyGetQDGlobals()->screenBits.bounds;
InsetRect(&limitRect, 1, kCtlStripFrame);
if (statusDevice == NULL || statusDevice == GetMainDevice())
limitRect.top += GetMBarHeight();
if (sBarRect.top - displaySize.v > limitRect.top) {
/*
* The calendar is displayed above the status bar.
* This is the preferred location.
*/
windowRect->bottom = sBarRect.top - kCtlStripFrame;
windowRect->top = windowRect->bottom - displaySize.v;
}
else {
/*
* The calendar is displayed below the status bar.
*/
windowRect->top = sBarRect.bottom + kCtlStripFrame;
windowRect->bottom = windowRect->top + displaySize.v;
}
/*
* Display the calendar to the right of the calendar icon, right justified
* against the display edge, if necessary.
*/
windowRect->left = sBarRect.left - kCtlStripFrame;
windowRect->right = windowRect->left + displaySize.h;
if (windowRect->right > limitRect.right)
OffsetRect(windowRect, limitRect.right - windowRect->right, 0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Build the triangular "next month" and "previous month" buttons.
*/
static void
MakeTriangularButtons(
const Rect *monthRect,
PolyHandle *leftButton,
PolyHandle *rightButton,
Rect *leftButtonRect,
Rect *rightButtonRect
)
{
FontInfo fontInfo;
short buttonSize;
short halfSize;
Rect bothButtonRect;
GetFontInfo(&fontInfo);
buttonSize = (fontInfo.ascent & ~1); /* Round down to even value */
halfSize = buttonSize / 2;
bothButtonRect = *monthRect;
bothButtonRect.bottom -= (1 + fontInfo.leading);
bothButtonRect.top = bothButtonRect.bottom - buttonSize;
#define kButtonSeparation 4 /* 1.0d3, was 2 */
bothButtonRect.left =
(width(*monthRect) >> 1) - buttonSize - kButtonSeparation;
bothButtonRect.right =
(width(*monthRect) >> 1) + buttonSize + kButtonSeparation;
*leftButtonRect = bothButtonRect;
/* 1.0d4 + */
leftButtonRect->right = leftButtonRect->left + halfSize;
*rightButtonRect = bothButtonRect;
rightButtonRect->left = rightButtonRect->right - halfSize;
*leftButton = OpenPoly();
MoveTo(halfSize, 0);
LineTo(halfSize, buttonSize);
LineTo(0, halfSize);
LineTo(halfSize, 0);
ClosePoly();
OffsetPoly(*leftButton, leftButtonRect->left, leftButtonRect->top);
*rightButton = OpenPoly();
MoveTo(0, 0);
LineTo(halfSize, halfSize);
LineTo(0, buttonSize);
LineTo(0, 0);
ClosePoly();
OffsetPoly(*rightButton, rightButtonRect->left, rightButtonRect->top);
/* 1.0d4 - */
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Now that we've done setup, do the actual mouse tracking. If the mouse hits the
* right button, advance the month and draw it. If it hits the left button, draw
* the previous month.
*/
static void
DrawCalendarAndTrackMouse(
GlobalPtr globalPtr,
WindowPtr windowPtr,
const Rect *monthRect
)
{
Rect leftButtonRect;
Rect rightButtonRect;
PolyHandle leftButton;
PolyHandle rightButton;
unsigned long nowSeconds;
DateTimeRec now;
Point mousePt;
short thisYear;
short thisMonth;
typedef enum {
inNoButton = 0,
inLeftButton = 1,
inRightButton = 2
} WhichButton;
WhichButton inButton;
WhichButton wasInButton;
unsigned long nextMonthTick;
Boolean redrawButtons;
Str255 dateString;
FrameRect(monthRect);
MakeTriangularButtons(
monthRect,
&leftButton,
&rightButton,
&leftButtonRect,
&rightButtonRect
);
pstrcpy(dateString, *GLOBAL.dateStringHandle);
GetDateTime(&nowSeconds);
Secs2Date(nowSeconds, &now);
inButton = wasInButton = inNoButton;
thisYear = thisMonth = 0;
InitCursor();
while (WaitMouseUp()) {
if (thisYear != now.year || thisMonth != now.month) {
/*
* Draw the new month. Also make sure the buttons are drawn.
*/
redrawButtons = TRUE;
EraseRect(&windowPtr->portRect);
FrameRect(monthRect);
switch (wasInButton) {
case inLeftButton:
FillPoly(leftButton, kWhitePattern);
break;
case inRightButton:
FillPoly(rightButton, kWhitePattern);
break;
}
FramePoly(leftButton);
FramePoly(rightButton);
DrawCalendar(
now.year,
now.month,
GLOBAL.firstDayOfWeek, /* Sunday */
dateString,
&windowPtr->portRect,
GLOBAL.fontNumber,
GLOBAL.fontSize
);
thisYear = now.year;
thisMonth = now.month;
} /* If drawing new month */
/*
* Get the mouse and track it while it is in one of our buttons
*/
GetMouse(&mousePt);
if (PtInRect(mousePt, &leftButtonRect))
inButton = inLeftButton;
else if (PtInRect(mousePt, &rightButtonRect))
inButton = inRightButton;
else {
inButton = inNoButton;
}
if (redrawButtons || inButton != wasInButton) {
switch (wasInButton) {
case inLeftButton: FillPoly(leftButton, kWhitePattern); break;
case inRightButton: FillPoly(rightButton, kWhitePattern); break;
}
switch (inButton) {
case inLeftButton: FillPoly(leftButton, kBlackPattern); break;
case inRightButton: FillPoly(rightButton, kBlackPattern); break;
}
FramePoly(leftButton);
FramePoly(rightButton);
if (inButton != wasInButton && inButton != inNoButton)
nextMonthTick = 0; /* Force new month drawing */
redrawButtons = FALSE;
wasInButton = inButton;
} /* If button click change */
if (inButton != inNoButton && TickCount() > nextMonthTick) {
/*
* The user has clicked in a button, or has held the mouse
* down in a button for one second. Draw the appropriate month.
*/
nextMonthTick = TickCount() + 60;
switch (inButton) {
case inLeftButton:
if (--now.month <= 0) { /* Previous month or year */
now.month = 12;
--now.year;
}
break;
case inRightButton:
if (++now.month > 12) { /* Next month or year */
now.month = 1;
++now.year;
}
break;
} /* Which button was clicked */
} /* Moving to a new month */
} /* Loop while mouse down */
KillPoly(leftButton);
KillPoly(rightButton);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Click in the status bar icon
*/
long
CtlStripMouseClick(
GlobalPtr globalPtr,
const Rect *statusRect
)
{
Rect windowRect;
WindowPtr windowPtr;
GrafPtr savePort;
Point displaySize;
Rect monthRect;
displaySize = GetCalendarDisplaySize(GLOBAL.fontNumber, GLOBAL.fontSize);
displaySize.h += 2;
displaySize.v += 2;
GetDisplayRect(statusRect, displaySize, &windowRect);
windowPtr = NewWindow(
NULL,
&windowRect,
"\p",
TRUE,
plainDBox,
(WindowPtr) -1L,
FALSE, /* No go-away box */
0 /* No refCon */
);
if (windowPtr != NULL) {
GetPort(&savePort);
SetPort(windowPtr);
GetCalendarMonthRect(
GLOBAL.fontNumber,
GLOBAL.fontSize,
&windowPtr->portRect,
&monthRect
);
if (StillDown()) {
/*
* Design the two triangular buttons that will be displayed on the
* bottom line of the calendar and create the polygons. Then draw
* the calendar and track the mouse while it's held down.
*/
DrawCalendarAndTrackMouse(
globalPtr,
windowPtr,
&monthRect
);
}
SetPort(savePort);
DisposeWindow(windowPtr);
}
return (0);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* Save current status for restarts.
*/
OSErr
CtlStripSavePreferences(
GlobalPtr globalPtr
)
{
OSErr status;
SavedSettingsHandle prefsHandle;
Str255 work;
#define PREF (**prefsHandle)
prefsHandle = (SavedSettingsHandle) NewHandleClear(sizeof (SavedSettings));
status = MemError();
if (status == noErr) {
HLock((Handle) prefsHandle);
PREF.signature = kApplicationCreator;
PREF.prefVersion = kPrefVersion;
GetFontName(GLOBAL.fontNumber, PREF.fontName);
PREF.fontSize = GLOBAL.fontSize;
PREF.firstDayOfWeek = GLOBAL.firstDayOfWeek; /* * 1.0d7 */
pstrcpy(PREF.dateString, *GLOBAL.dateStringHandle);
SBGetDetachedIndString(work, GLOBAL.textStrings, kStringPreference);
status = SBSavePreferences(work, (Handle) prefsHandle);
DisposeHandle((Handle) prefsHandle);
}
return(status);
}
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* We don't do anything interesting for periodic tickles.
*/
long
CtlStripPeriodicTickle(
GlobalPtr globalPtr,
const Rect *statusRect
)
{
if (0) { /* Unused variables */
globalPtr;
statusRect;
}
return (0);
}